package com.titar.robot.repository;



import com.titar.robot.AppExecutors;
import com.titar.robot.base.BaseModel;
import com.titar.robot.http.ApiEmptyResponse;
import com.titar.robot.http.ApiErrorResponse;
import com.titar.robot.http.ApiResponse;
import com.titar.robot.http.ApiSuccessResponse;


import java.io.Serializable;

import androidx.annotation.MainThread;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.Observer;

import static com.titar.robot.Constants.RET_SUCCESS_0;

public abstract class NetWorkBoundResource<ResultType extends Serializable, RequestType extends BaseModel<ResultType>> {
    private MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData();

    private AppExecutors appExecutors;

    public NetWorkBoundResource(AppExecutors appExectors) {
        this.appExecutors = appExectors;
        result.setValue(Resource.loading(null));

        LiveData<ResultType> dbSource = loadFromDb();
        result.addSource(dbSource, new Observer<ResultType>() {
            @Override
            public void onChanged(ResultType resultType) {
                result.removeSource(dbSource);
                if (shouldFetch(resultType)) {
                    fetchFromNetwork(dbSource);
                } else {
                    result.addSource(dbSource, new Observer<ResultType>() {
                        @Override
                        public void onChanged(ResultType resultType) {
                            setValue(Resource.success(resultType));
                        }
                    });
                }
            }
        });
    }

    public LiveData<Resource<ResultType>> asLiveData() {
        return result;
    }

    public void onFetchFailed() {
    }

    @MainThread
    private void setValue(Resource<ResultType> newValue) {
        if (result.getValue() != newValue) {
            result.setValue(newValue);
        }
    }

    private void fetchFromNetwork(LiveData<ResultType> dbSource) {
        final LiveData<ApiResponse<RequestType>> apiResponse = createCall();
        result.addSource(dbSource, new Observer<ResultType>() {
            @Override
            public void onChanged(ResultType resultType) {
                setValue(Resource.loading(resultType));
            }
        });
        result.addSource(apiResponse, new Observer<ApiResponse<RequestType>>() {
            @Override
            public void onChanged(ApiResponse<RequestType> response) {
                result.removeSource(apiResponse);
                result.removeSource(dbSource);
                if (response instanceof ApiSuccessResponse) {
                    BaseModel baseModel = ((ApiSuccessResponse<RequestType>) response).getBody();
                    if (baseModel.getErrcode() == RET_SUCCESS_0) {
                        if (shouldCache()) {
                            appExecutors.getDiskIO().execute(new Runnable() {
                                @Override
                                public void run() {
                                    saveCallResult(processResponse((ApiSuccessResponse<RequestType>) response));
                                    appExecutors.getMainThread().execute(new Runnable() {
                                        @Override
                                        public void run() {
                                            result.addSource(loadFromDb(), new Observer<ResultType>() {
                                                @Override
                                                public void onChanged(ResultType resultType) {
                                                    setValue(Resource.success(resultType));
                                                }
                                            });
                                        }
                                    });
                                }
                            });
                        } else {
                            result.setValue(Resource.success((ResultType) baseModel.data));
                        }
                    } else {
                        onFetchFailed();
                        result.addSource(dbSource, new Observer<ResultType>() {
                            @Override
                            public void onChanged(ResultType resultType) {
                                setValue(Resource.result_error(baseModel.getErrmsg(), resultType));
                            }
                        });
                    }

                }
                if (response instanceof ApiEmptyResponse) {
                    appExecutors.getMainThread().execute(new Runnable() {
                        @Override
                        public void run() {
                            result.addSource(loadFromDb(), new Observer<ResultType>() {
                                @Override
                                public void onChanged(ResultType resultType) {
                                    setValue(Resource.success(resultType));
                                }
                            });
                        }
                    });
                }

                if (response instanceof ApiErrorResponse) {
                    onFetchFailed();
                    result.addSource(dbSource, new Observer<ResultType>() {
                        @Override
                        public void onChanged(ResultType resultType) {
                            setValue(Resource.error(((ApiErrorResponse<RequestType>) response).getErrorMessage(), resultType));
                        }
                    });
                }
            }
        });
    }


    @WorkerThread
    protected RequestType processResponse(ApiSuccessResponse<RequestType> response) {
        return response.getBody();
    }

    @WorkerThread
    protected abstract void saveCallResult(RequestType item);

    @MainThread
    protected abstract Boolean shouldFetch(ResultType data);

    @MainThread
    protected Boolean shouldCache() {
        return false;
    }

    @MainThread
    protected abstract LiveData<ResultType> loadFromDb();

    @MainThread
    protected abstract LiveData<ApiResponse<RequestType>> createCall();
}
